#include <stdlib.h>
#include <stdio.h>
#if defined(__APPLE__) && defined(__MACH__)
# include <GLUT/glut.h>
#else
#  include <GL/glut.h>
#  include <windows.h>
#endif

#include <iostream>
#include <math.h>

using namespace std;

int windowWidth = 800;
int windowHeight = 600;
char s[90];

float _vertices2[12] = { -0.5, 0.0, (1 / 3.4641), 0.5, 0.0, (1 / 3.4641), 0.0,
		0.8165, 0.0, 0.0, 0.0, (-1 / 1.7321) };
float _vertices[24] = { -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0,
		-1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0,
		1.0, 1.0 };

float _AxisTheta[3] = { 0.0, 0.0, 0.0 };
float _SelfRotateTheta[3] = { 0.0, 0.0, 0.0 };

GLubyte _indices[24] = { 0, 3, 2, 1, 2, 3, 7, 6, 0, 4, 7, 3, 1, 2, 6, 5, 4, 5,
		6, 7, 0, 1, 5, 4 };

GLubyte _indices2[12] = { 0, 1, 2, 2, 1, 3, 0, 3, 1, 2, 3, 0, };

int _axis = 1;
float PI = 3.1415926;
int xi = 0;
int mouseLocX, mouseLocY = 0;

int myframe, mytime, timebase = 0;
float fps = 0.0;
GLfloat diffuse0[] = { 0.2, 0.2, 0.2, 1.0 };
GLfloat ambient0[] = { 0.1, 0.1, 0.1, 1.0 };
GLfloat specular0[] = { 1.0, 0.0, 0.0, 1.0 };
GLfloat light0_pos[] = { 1.0, 2.0, 3, 0, 1.0 };

float colorGreen[4] = { 0.0, 1.0, 0.0, 1.0 };
float colorYellow[4] = { 1.0, 1.0, 0.0, 1.0 };
float colorNone[4] = { 0.0, 0.0, 0.0, 1.0 };
float colorRed[4] = { 1.0, 0.0, 0.0, 1.0 };
float radius = 1.1;
float step = 0.1;
const int N = 20;
// This tells us if we want lines or fill mode
bool g_bRenderMode = true;

GLdouble Ofront[3] = { 0.0, 0.0, 1.0 };
GLdouble Oback[3] = { 0.0, 0.0, -1.0 };

GLdouble curPoints[N][3], prePoints[N][3];

void drawSurface(double *v1, double *v2, double *v3, double *v4) {
	glBegin(GL_POLYGON);
	glNormal3dv(v1);
	glVertex3dv(v1);

	glNormal3dv(v2);
	glVertex3dv(v2);

	glNormal3dv(v3);
	glVertex3dv(v3);

	glNormal3dv(v4);
	glVertex3dv(v4);
	glEnd();
}

void init() {

	for (int i = 0; i < N; i++) {
		curPoints[i][0] = prePoints[i][0] = 0.0;//x
		curPoints[i][1] = prePoints[i][1] = 0.0;//y
		curPoints[i][2] = prePoints[i][2] = 1.0;//z
	}
	glShadeModel(GL_SMOOTH);

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_NORMALIZE);

	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);

}
void drawSphere() {
	int i = 0;
	double theta = 2 * PI / N;
	GLdouble d = radius;
	while (d >= -radius) {
		d -= step;
		GLdouble r = sqrt(radius * radius - d * d);
		for (i = 0; i < N; i++) {
			curPoints[i][0] = (GLdouble) r * cos(theta * (i + 1));//x
			curPoints[i][1] = (GLdouble) r * sin(theta * (i + 1));//y
			curPoints[i][2] = d; //z
		}

		for (i = 0; i < N; i++)
			drawSurface(&curPoints[i][0], &curPoints[i + 1][0],
					&prePoints[i + 1][0], &prePoints[i][0]);
		drawSurface(&curPoints[N - 1][0], &curPoints[0][0], &prePoints[0][0],
				&prePoints[N - 1][0]);
		for (int i = 0; i < N; i++) {
			prePoints[i][0] = curPoints[i][0];//x
			prePoints[i][1] = curPoints[i][1];//y
			prePoints[i][2] = curPoints[i][2];//z
		}
	}

}
void drawCube() {
	glVertexPointer(3, GL_FLOAT, 0, _vertices);
	//    glColorPointer(3, GL_FLOAT, 0, _colors);
	glNormalPointer(GL_FLOAT, 0, _vertices);
	glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, _indices);

}

void drawTetrahedron() {
	glVertexPointer(3, GL_FLOAT, 0, _vertices2);
	//glColorPointer(3, GL_FLOAT, 0, _colors2);
	glNormalPointer(GL_FLOAT, 0, _vertices2);
	glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_BYTE, _indices2);

}

void display() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	// Change the rendering mode to and from lines or triangles
	if (g_bRenderMode) {
		// Render the triangles in fill mode
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	} else {
		// Render the triangles in wire frame mode
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
	glTranslatef(0.0, 0.0, -10.0);

	glRotatef(_AxisTheta[0], 1.0, 0.0, 0.0);
	glRotatef(_AxisTheta[1], 0.0, 0.1, 0.0);
	// glRotatef(_AxisTheta[2], 0.0, 0.0, 1.0);

	drawSphere();

	glTranslatef(6.0, 0.0, 0.0);
	glPushMatrix();

	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, colorYellow);
	drawCube();

	//  glLoadIdentity();
	glScalef(1.7, 1.7, 1.7);
	//  glRotatef(_SelfRotateTheta[0], 1.0, 0.0, 0.0);
	glRotatef(_SelfRotateTheta[1], 0.0, 0.1, 0.0);
	// glRotatef(_SelfRotateTheta[2], 0.0, 0.0, 1.0);
	glTranslatef(2.0, -0.5, 0.0);
	drawTetrahedron();
	glTranslatef(-4.0, 0.0, 0.0);
	drawTetrahedron();

	glPopMatrix();

	glTranslatef(-12.0, 0.0, 0.0);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, colorGreen);
	drawCube();
	glScalef(1.7, 1.7, 1.7);
	glRotatef(-_SelfRotateTheta[1], 0.0, 0.1, 0.0);
	// glRotatef(_SelfRotateTheta[2], 0.0, 0.0, 1.0);
	glTranslatef(2.0, -0.5, 0.0);
	drawTetrahedron();
	glTranslatef(-4.0, 0.0, 0.0);
	drawTetrahedron();

	myframe++;// used to calculate and display frame rate on window title.
	mytime = glutGet(GLUT_ELAPSED_TIME);
	if (mytime - timebase > 1000) {
		fps = myframe * 1000.0 / (mytime - timebase);
		sprintf(s, "Assignment 2 by Zhiyi Wu FPS:%4.2f", fps);

		glutSetWindowTitle(s);
		timebase = mytime;
		myframe = 0;
	}

	glutSwapBuffers();

}

void spinCube() {
	_AxisTheta[_axis] += 1.1;
	_SelfRotateTheta[_axis] += 1.1;

	if (_AxisTheta[_axis] > 360.0)
		_AxisTheta[_axis] -= 360.0;
	if (_SelfRotateTheta[_axis] > 360.0)
		_SelfRotateTheta[_axis] -= 360.0;

	glutPostRedisplay();
}

void mouse(int btn, int state, int x, int y) {
	mouseLocX = x;
	mouseLocY = y;

}

void mykey(unsigned char key, int x, int y) //used to clear the canvas
{
	if ((key == 'C') | (key == 'c')) {
		glutPostRedisplay();
	}
	if ((key == 'W') | (key == 'w')) {
		// We want to be able to turn wire frame on and off, so let's switch it
		g_bRenderMode = !g_bRenderMode;
	}
}

void motion(int x, int y) { //control the draw function when mouse button down and move


	if (mouseLocX != x) {
		_AxisTheta[1] += (x - mouseLocX);

	}
	if (mouseLocY != y) {
		_AxisTheta[0] += (y - mouseLocY);

	}
	mouseLocX = x;
	mouseLocY = y;

}

void AlternativePerspective(GLfloat fovy, GLfloat aspect, GLfloat zNear,
		GLfloat zFar) {
	GLfloat xmin, xmax, ymin, ymax;

	ymax = zNear * tan(fovy * PI / 360.0);
	ymin = -ymax;
	xmin = ymin * aspect;
	xmax = ymax * aspect;

	glFrustum(xmin, xmax, ymin, ymax, zNear, zFar);
}
void myReshape(int w, int h) {
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);//Applies subsequent matrix operations to the modelview matrix stack. this is used to contril the
	glLoadIdentity();//glLoadIdentity replaces the current matrix with the identity matrix.
	glOrtho(-2.0, 2.0, -2.0, 2.0, 2.0, -2.0);
	AlternativePerspective(50.0, (float) w / (float) h, 2.0, 10.0);

	glMatrixMode(GL_MODELVIEW);//Applies subsequent matrix operations to the projection matrix stack.

}

int main(int argc, char** argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH) - windowWidth) / 2,
			(glutGet(GLUT_SCREEN_HEIGHT) - windowHeight) / 2); // Set the windows centered in screen based on native resolution.

	glutCreateWindow("Assignment 2 by Zhiyi Wu");
	init();

	//	glEnableClientState( GL_COLOR_ARRAY); //this one cause the total damage.
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);

	glutReshapeFunc(myReshape);
	glutDisplayFunc(display);
	glutIdleFunc(spinCube);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutKeyboardFunc(mykey);
	glEnable(GL_DEPTH_TEST);

	glutMainLoop();
	return 0;
}
